import config from '@/config'
import { createId, createPromise, normalizeModel } from '@/utils'

import {
  ActorData,
  GetWidgetComponent,
  WidgetData,
  WidgetKind,
  WidgetMode,
  WidgetStyle,
  WidgetType,
  WidgetSource,
  WidgetOption,
  DateSign,
  DateFormat,
  SignData,
  TextData,
  AttachmentData,
  Viewer,
  ImageData,
  ImageDefault,
  TextDefault,
  TableOptions,
  TableDefault,
  TableField,
  TableHeaderPosition,
  TableItem,
  PageData,
  TagProps
} from '@/types'

import CheckOption from '../CheckOption'

import { defineWidget, getWidgetOption, getWidgetOptions } from './option'
import { setWidgetTransfer, getWidgetTransfer } from './transfer'
import { createRawWidget, getBatchField, getRawField } from './rawField'
import WidgetBox from '../WidgetBox'
import { adjustLocation, getDropLocation, getPageRange, getRange, moveWidget } from './location'
import { adjustSize, getSizeRange, setHeight, setWidth } from './size'
import { getApplyCount, getBatchApplyCount, getWidgetPages, isWidgetInPage } from './batch'
import { getLinkDocs, getLinkPageRange, getLinkWidgets, syncLinkWidgets } from './link'
import { cloneWidget } from './utils'

import {
  addTableData,
  addTableField,
  getTableColWidths,
  getTableCols,
  getTableColsMax,
  getTableRows,
  getTableRowsHeightMax,
  getTableRowsMax,
  getTableSize,
  getTableValue,
  isTableOutOfPage,
  removeTableData,
  removeTableField,
  setTableColWidth,
  setTableColWidths,
  setTableCols,
  setTableRows,
  tableAutoCrop,
  tableValidate,
  updateTableDataLength,
  updateTableFieldsLength
} from '@/widgets/table/utils'

/** 数字化字符串值 */
// eslint-disable-next-line
function numberlize(object: any, keys: string[]) {
  if (!(object && object instanceof Object)) return

  keys.forEach(key => {
    const val = object[key]
    if (typeof val === 'string') object[key] = +val
  })
}

/**
 * 控件数据模型
 * @class Widget
 */
export default class Widget extends WidgetBox {
  /**
   * Widget 构造
   * @param {object} data 控件数据
   * @memberof Widget
   */
  constructor(data: WidgetData) {
    super()

    const { promise: initPromise, resolve: initResolve } = createPromise()

    this.initPromise = initPromise

    this.init(data)

    initResolve?.()
  }

  /** 初始化数据的 promise */
  initPromise?: Promise<void>

  /** 初始化数据 */
  init(raw: WidgetData) {
    const data = normalizeModel(raw) as WidgetData

    // 数字类型兼容字符串
    numberlize(data, ['type', 'p', 'w', 'h', 'ox', 'oy'])
    numberlize(data.text, ['fontSize', 'fontFamily'])

    const {
      default: widgetDefault = {},
      init: WidgetInit,
      width: w,
      height: h
    } = getWidgetOption(data.type ?? WidgetType.UNKNOWN)

    Object.assign(this, widgetDefault, { w, h }, data)

    WidgetInit?.(this, data)

    this.updateOrigin()
  }

  /** 控件 id，如果未设置则自动生成 */
  id = createId()

  /** 控件名称 */
  name = ''

  /** 控件展示名称，如未设置控件名称，则显示控件选项的名称 */
  get displayName() {
    return this.name || this.option.name
  }

  /** 控件元素类名，用于自定义控件类名 */
  className = ''

  /** 控件类型 */
  type = WidgetType.UNKNOWN

  /** 是否填写控件 */
  get isFill() {
    return this.option.style === WidgetStyle.FILL
  }

  /** 是否签署控件 */
  get isSign() {
    return this.option.style === WidgetStyle.SIGN
  }

  /** 控件配置选项 */
  get option() {
    return getWidgetOption(this.type)
  }

  /** 控件组件 */
  get component() {
    const { component } = this.option

    if (typeof component === 'function') {
      return (component as GetWidgetComponent)(this)
    } else {
      return component
    }
  }

  /** 控件模式，用于区分控件的状态 */
  mode = WidgetMode.USE

  /** 是否**设置**模式，如指定控件位置和设置控件信息 */
  get isSet() {
    return this.mode === WidgetMode.SET
  }

  /** 是否**使用**模式，如填写和签署 */
  get isUse() {
    return this.mode === WidgetMode.USE
  }

  /** 是否**查看**模式，如控件预览 */
  get isView() {
    return this.mode === WidgetMode.VIEW
  }

  /** 是否**完成**模式，如预览已填写定稿的控件 */
  get isDone() {
    return this.mode === WidgetMode.DONE
  }

  /** 控件种类 */
  kind = WidgetKind.NORMAL

  /** 参与方，引用值，应该直接赋值而非修改子属性 */
  actor: ActorData = {
    id: '',
    name: '',
    index: -1
  }

  /** 参与方颜色 */
  get actorColor() {
    return Widget.getActorColor(this.actor.index ?? -1)
  }

  /**
   * 控件 label 文案
   * @description 使用阶段：控件名称 (提示)
   * @description 其他阶段：参与方 (提示)
   */
  get label() {
    let label = this.isUse ? this.displayName : this.actor.name || '未指定参与方'
    const { labelTips } = this

    labelTips && (label += ` (${labelTips})`)

    return label
  }

  /** 控件使用中 label 文案，用于快捷填写 */
  get labelUsing() {
    let label = this.displayName
    const { labelTips } = this

    labelTips && (label += ` (${labelTips})`)

    return label
  }

  /** 控件 label 次级提示 */
  get labelTips() {
    return this.option.labelTips?.(this) || ''
  }

  /** 控件提示信息 */
  tips = ''

  /** 额外数据，用于扩展额外的字段 */
  extra: Record<string, unknown> = {}

  /**
   * 尺寸和定位
   */

  /** 绑定的合同控件实例 */
  viewer: Viewer | null = null

  /** 控件所在文档 id */
  docId = ''

  /** 控件所在文档 */
  get doc() {
    return this.viewer?.state.docs.find(doc => doc.id === this.docId)
  }

  /** 控件所在页码，从 0 开始  */
  p = 0

  /**
   * 控件所在页面
   * @description 骑缝控件关联第一页数据
   */
  get page() {
    return (
      this.doc?.pages[this.p] ||
      ({
        w: config.page.width,
        h: config.page.height,
        p: this.p
      } as PageData)
    )
  }

  /**
   * 应用的页面，用于批量和骑缝控件
   * @description
   * - `undefined`: 全部页面
   * - `'ODD'`: 奇数页
   * - `'EVEN'`: 偶数页
   * - `'1, 2, 5-8'`: 指定页（仅批量控件，从 1 开始）
   */
  applyPage?: string

  /** 所在页面列表 (谨慎使用，避免超多页面文档性能问题) */
  get pages() {
    return getWidgetPages(this)
  }

  /** 控件是否在指定页 */
  isInPage(p: number, docId = this.docId) {
    return isWidgetInPage(this, p, docId)
  }

  /**
   * 控件关联 id
   * @description
   * 骑缝、批量控件应用到其它文档时通过 `linkId` 关联
   */
  linkId = ''

  /** 关联的控件 (不包含当前控件) */
  get linkWidgets() {
    return getLinkWidgets(this)
  }

  /** 关联控件所在文档列表 (包含当前控件文档) */
  get linkDocs() {
    return getLinkDocs(this)
  }

  /** 获取关联控件所在文档最小页面范围 */
  get linkPageRange() {
    return getLinkPageRange(this)
  }

  /** 获取控件批量应用个数 */
  get batchApplyCount() {
    return getBatchApplyCount(this)
  }

  /** 控件中心定位 x */
  set ox(val) {
    // 骑缝控件 x 定位 0
    if (this.isRiding) {
      this.x = 0
      return
    }

    const x = val - this.w / 2
    this.x = x > 0 ? x : 0
  }

  get ox() {
    // 骑缝控件 x 定位 0
    if (this.isRiding) return 0

    return this.x + this.w / 2
  }

  /** 控件原始中心定位，用于以中心位置调整大小时记录中心位置 */
  _origin?: {
    x: number
    y: number
  }

  /** 更新中心点位置 */
  updateOrigin() {
    this._origin = {
      x: this.ox,
      y: this.oy
    }
  }

  /** 还原中心点位置 */
  restoreOrigin() {
    if (!this._origin) return

    const { x, y } = this._origin

    Object.assign(this, {
      ox: x,
      oy: y
    })

    this.viewer && this.adjust()
  }

  /** 宽高比例，0 则不约束 */
  aspectRatio = 0

  /** 定位范围 */
  get range() {
    return getRange(this)
  }

  /** 获取控件最小页面范围 */
  get pageRange() {
    return getPageRange(this)
  }

  /** 尺寸范围 */
  get sizeRange() {
    return getSizeRange(this)
  }

  /** 是否激活的控件 */
  get isActivated() {
    return this.viewer?.activeWidget === this
  }

  /** 设置激活的控件 */
  set isActivated(val) {
    const { viewer } = this

    if (!viewer || (!val && viewer.activeWidget !== this)) return

    viewer.activeWidget = val ? this : undefined
  }

  /**
   * 操作控制
   */

  /**
   * 是否指定位置
   * @description 场景: 指定位置签署
   */
  fixed = false

  /**
   * 是否可以删除
   * @description 如果 fixed = true，则此字段不生效
   */
  deletable = true

  /**
   * 是否固定控件
   * @description 表现：不允许删除、移动、调整尺寸
   * @description 场景： 1. 指定位置；2. 查看模式；3. 完成模式；4. 填写控件使用模式
   */
  get isFixed() {
    return this.fixed || this.isView || this.isDone || (this.isFill && this.isUse)
  }

  /** 是否允许调整大小 */
  get resizable() {
    let { resizable } = this.option

    if (typeof resizable === 'boolean') return this.isSet && resizable

    if (typeof resizable === 'function') return !!resizable(this)

    return false
  }

  /** 是否允许删除 */
  get removable() {
    if (this.isFixed) return false

    return this.deletable
  }

  /**
   * 错误信息
   */

  /** 校验错误信息 */
  get error() {
    // 仅设置和使用模式才校验
    if (!(this.isSet || this.isUse)) return

    /** 自定义错误 */
    if (this.customError) return this.customError

    /** 设置控件 */
    if (this.isSet) return this.errorSetting

    /** 使用控件 */
    if (this.isUse) return this.errorUsing
  }

  /** 参与方是否必须 */
  get actorRequired() {
    const check = this.viewer?.widgetActorRequired

    if (typeof check === 'boolean') return check

    if (typeof check === 'function') return check(this)

    return true
  }

  /** 控件名称是否必须 */
  get nameRequired() {
    const check = this.viewer?.widgetNameRequired

    if (typeof check === 'boolean') return check

    if (typeof check === 'function') return check(this)

    return true
  }

  /** 设置错误 */
  get errorSetting() {
    // 未指定参与方
    if (!this.actor.id && this.actorRequired) {
      return `请设置参与方信息`
    }

    // 未指定控件名称
    if (!this.name && this.nameRequired) {
      return `请设置控件名称`
    }

    // 设置校验
    const valid = this.option.settingValidate?.(this)

    if (valid) return valid
  }

  /** 使用错误 */
  get errorUsing() {
    // 使用校验
    const valid = this.option.usingValidate?.(this)

    if (valid) return valid
  }

  /** 自定义错误消息 */
  customError = ''

  /**
   * 签署控件
   */

  /** 是否骑缝控件 */
  get isRiding() {
    return this.kind === WidgetKind.RIDING
  }

  /**
   * 是否骑缝样式
   * @description 骑缝控件完成模式或非完成模式骑缝区域收拢，控件按页面数量平均宽度展示
   */
  get isRidingStyle() {
    if (!this.isRiding) return false

    return this.isDone || (!this.isDone && !this.viewer?.layout.ridingActive)
  }

  /**
   * 展示宽度
   * 骑缝控件在骑缝区域收拢或控件完成模式的宽度为页面数量平均
   */
  get displayWidth() {
    if (!this.isRidingStyle) return this.w

    const { widgetDefaultWidth, widgetMinWidth } = config.riding

    const total = this.doc?.pages.length || 0

    if (!total) return widgetDefaultWidth

    const applyTotal = getApplyCount(total, this.applyPage)

    return Math.max(Math.floor(this.w / applyTotal), widgetMinWidth)
  }

  /** 是否批量签章 */
  get isBatch() {
    return this.kind === WidgetKind.BATCH
  }

  /**
   * 是否允许批量应用
   * - 骑缝控件、批量控件非指定位置允许批量应用
   */
  get batchable() {
    return !this.isFixed
  }

  /** 签章数据，设置模式用来展示签章 */
  _sign: SignData | DateSign = {
    id: '',
    img: ''
  }

  /** 设置印章数据 */
  set sign(sign: SignData | DateSign) {
    this._sign = sign

    this.initPromise?.then(() => this.updateSizeBySign())
  }

  /** 签章尺寸 */
  get signSize() {
    const { sign, option } = this

    const w = (sign.w as number) || option.width
    const h = (sign.h as number) || option.height

    return { w, h }
  }

  /** 获取印章数据 */
  get sign() {
    return this._sign
  }

  /** 签章控件是否跟随签章图片规格尺寸 */
  followSignSize = true

  /**
   * 签章控件自定义规格时是否保持比例，
   * `followSignSize` 为 `false` 时生效
   */
  keepSignAspect = false

  /** 根据签章更新控件尺寸 */
  updateSizeBySign() {
    /**
     * 签名印章控件跟随签章尺寸
     */
    if (![WidgetType.SIGNATURE, WidgetType.SEAL].includes(this.type) || !this.followSignSize || !this.sign.id) return

    const { w, h } = this.signSize

    Object.assign(this, {
      // 保持签章比例
      aspectRatio: w / h,
      w,
      h
    })

    this.restoreOrigin()
  }

  /** 签署控件图片 */
  get signImage() {
    let { img } = this.sign

    if (img) return img

    const dateSignSrc = this.viewer?.signOptions.dateSignSrc

    // 日期控件图片
    if (dateSignSrc && this.type === WidgetType.DATE) {
      return (typeof dateSignSrc === 'string' ? dateSignSrc : dateSignSrc?.(this)) || ''
    }

    return ''
  }

  /** 签署时是否允许调整签章尺寸，默认为 false */
  get signResizable() {
    if (!this.isUse || this.fixed) return false

    let resizable = this.viewer?.signOptions.signResizable ?? false

    if (typeof resizable === 'function') {
      return resizable(this) ?? false
    }

    return resizable
  }

  /** 签署时是否允许更换签章 */
  get signChangeable() {
    if (!this.isUse) return false

    const signChangeable = this.viewer?.signOptions.signChangeable

    return signChangeable ? signChangeable(this) : true
  }

  /** 指定印章ID */
  assignedSignId = ''

  /** 指定印章名称 */
  assignedSignName = ''

  /** 签章控件标签 */
  signTag?: string | TagProps

  /**
   * 填写控件
   */

  /** 填写值，附件则为附件文件 id */
  value = ''

  /** 检测控件是否有填写值 */
  get hasFillValue() {
    const { hasFillValue } = this.option

    return hasFillValue ? hasFillValue(this) : !!this.value
  }

  /** 是否必填 */
  required = true

  /** 控件数据来源 */
  source?: WidgetSource | string

  /** 可用来源列表 */
  get sources() {
    return this.viewer?.widgetSources?.(this) || []
  }

  /** 匹配到的控件数据来源项 */
  get sourceItem() {
    return this.sources.find(s => s.key === this.source)
  }

  /** **文本控件**文本信息 */
  text: TextData = { ...TextDefault }

  /** 字号大小值 */
  get fontSizeValue() {
    return this.text?.fontSize ? `${this.text?.fontSize}px` : ''
  }

  /** 字体样式值 */
  get fontFamilyValue() {
    const [, val] = config.fontFamilies.find(([key]) => key + '' === '' + this.text?.fontFamily) || []
    return val || TextDefault.fontFamily
  }

  /** **数字控件**是否限制数字小数点位数 */
  numberFixedLimit = false

  /** **数字控件**保留小数点位数，0 ~ 10 */
  numberFixed = 2

  /** **数字控件**是否显示千分位 */
  thousands = false

  /** **日期填写控件**日期格式 */
  dateFormat = DateFormat.LOCAL

  /** **单选框、复选框**选项 */
  checkOptions: CheckOption[] = []

  /** 单、复选框当前选项 */
  get activeOption(): CheckOption | undefined {
    return this.checkOptions[this.extra.activeIndex as number]
  }

  /** **下拉选择**选项 */
  selectOptions: string[] = []

  /** 附件控件 */
  attachment: AttachmentData = {
    fileName: '',
    accept: '',
    loading: false,
    progress: 0
  }

  /** 图片控件 */
  image: ImageData = ImageDefault

  /** 表格控件选项  */
  tableOptions: TableOptions = { ...TableDefault }

  /** 表格控件字段 */
  tableFields: TableField[] = []

  /** 表格数据 */
  tableData: TableItem[] = []

  /** 表格值 */
  get tableValue() {
    return getTableValue(this)
  }

  /** 表格控件列宽 */
  get tableColWidths() {
    return getTableColWidths(this)
  }

  /** 表格控件列宽 */
  set tableColWidths(widths: number[]) {
    setTableColWidths(this, widths)
  }

  /** 设置表格控件列宽 */
  setTableColWidth(index: number, width: number) {
    setTableColWidth(this, index, width)
  }

  /** 是否横向表格 */
  get isHorizontalTable() {
    return this.tableOptions.headerPosition === TableHeaderPosition.TOP
  }

  /** 是否纵向表格 */
  get isVerticalTable() {
    return this.tableOptions.headerPosition === TableHeaderPosition.LEFT
  }

  /** 表格数据行数，不含表头 */
  get tableRows() {
    return getTableRows(this)
  }

  /** 设置表格数据行数 */
  set tableRows(length: number) {
    setTableRows(this, length)
  }

  /** 表格数据最大行数 */
  get tableRowsMax() {
    return getTableRowsMax(this)
  }

  /** 表格数据最大行高 */
  get tableRowsHeightMax() {
    return getTableRowsHeightMax(this)
  }

  /** 表格数据列数，不含表头 */
  get tableCols() {
    return getTableCols(this)
  }

  /** 设置时表格数据列数 */
  set tableCols(length: number) {
    setTableCols(this, length)
  }

  /** 表格数据最大列数 */
  get tableColsMax() {
    return getTableColsMax(this)
  }

  /** 添加表格字段 */
  addTableField(index?: number, field?: TableField) {
    addTableField(this, index, field)
  }

  /** 移除表格字段 */
  removeTableField(index: number) {
    removeTableField(this, index)
  }

  /** 通过设置长度更新字段列表 */
  updateTableFieldsLength(length: number) {
    updateTableFieldsLength(this, length)
  }

  /** 添加表格数据项 */
  addTableData(index?: number, item?: TableItem) {
    addTableData(this, index, item)
  }

  /** 移除表格数据项 */
  removeTableData(index: number) {
    removeTableData(this, index)
  }

  /** 通过设置长度更新数据列表 */
  updateTableDataLength(length: number) {
    updateTableDataLength(this, length)
  }

  /** 表格尺寸 */
  get tableSize() {
    return getTableSize(this)
  }

  /** 更新表格控件尺寸 */
  updateTableSize() {
    Object.assign(this, this.tableSize)
  }

  /** 表格是否超出页面 */
  get tableOutOfPage() {
    return isTableOutOfPage(this)
  }

  /**
   * 表格自动裁剪，用于超出页面范围时删除超出的行列
   * adjust 为 `true` 会尝试更改行高和列宽为默认
   */
  tableAutoCrop(adjust = true) {
    tableAutoCrop(this, adjust)
  }

  /** 表格填写错误 */
  get tableFillError() {
    return tableValidate(this)
  }

  /** 清除控件数据 */
  clear() {
    Object.assign(this, {
      value: '',
      customError: ''
    } as Partial<Widget>)

    this.option.clear?.(this)
  }

  /** 生成原始控件数据（提供给后端） */
  getRawField() {
    return getRawField(this)
  }

  /** 生成批量控件的原始数据，将单个批量控件按页码生成多份 */
  getBatchField() {
    return getBatchField(this)
  }

  /** 控件按距离移动 */
  move(x = 0, y = 0, adjust = true) {
    moveWidget(this, x, y, adjust)
  }

  /** 控件移动到指定位置 */
  moveTo(x = this.x, y = this.y, adjust = true) {
    this.move(x - this.x, y - this.y, adjust)
  }

  /** 设置控件宽度 */
  setWidth(w: number, adjust = true) {
    setWidth(this, w, adjust)
  }

  /** 设置控件高度 */
  setHeight(h: number, adjust = true) {
    setHeight(this, h, adjust)
  }

  /** 适应位置和尺寸以免超出边界 */
  adjust() {
    adjustLocation(this)
    adjustSize(this)
  }

  /** 修复跨页移动后的坐标和页码 */
  fixDropLocation() {
    Object.assign(this, getDropLocation(this))
  }

  /** 克隆控件，生成的新控件会丢弃 extra 数据 */
  clone() {
    return cloneWidget(this)
  }

  /** 同步关联控件 */
  syncLinkWidgets() {
    syncLinkWidgets(this)
  }

  /**
   * 定义控件选项
   * @description 用以更改控件选项，或者自定义新的控件
   * @param {WidgetType | number} type 控件类型
   * @param {Partial<WidgetOption>} option 控件选项
   */
  static defineWidget = defineWidget

  /**
   * 获取控件配置选项
   * @param {WidgetType} type 控件类型
   * @return {WidgetOption} 控件选项
   */
  static getWidgetOption = getWidgetOption

  /**
   * 获取控件配置选项列表
   * @return {WidgetOption[]} 控件配置选项列表
   */
  static getWidgetOptions = getWidgetOptions

  /**
   *  根据后端记录的 JSON 原始控件数据创建控件
   * @param {RawField} data 后端记录的原始控件数据
   * @param {WidgetData} extra 额外的控件数据
   * @returns {Widget} 控件
   */
  static createRawWidget = createRawWidget

  /**
   * 设置拖拽的控件数据（拖拽开始）
   * @description 控件默认为设置模式，如果是拖动加盖印章，需要设置控件数据 `mode: WidgetMode.USE`
   * @param {event} e drag 事件
   * @param {WidgetData} data 控件数据
   */
  static setWidgetTransfer = setWidgetTransfer

  /**
   * 获取拖拽的控件数据（拖入）
   * @param {DragEvent} e drop 事件
   * @param {Element} area 拖拽区域节点
   * @param {number} zoom 缩放级别
   * @returns {WidgetData} 控件数据
   */
  static getWidgetTransfer = getWidgetTransfer

  /**
   * 获取参与方颜色
   * @param {number} index 参与方索引
   * @return `color01` ~ `color12`
   */
  static getActorColor(index: number) {
    const colorNo = (index % 12) + 1
    return `color${colorNo < 10 ? '0' + colorNo : colorNo}`
  }
}

Object.defineProperty(Widget.prototype, 'tableColWidths', { enumerable: true })
